View Javadoc

1   /*
2   @See License.txt@
3    */
4   
5   package spellcast.ui;
6   
7   import java.io.ByteArrayInputStream;
8   import java.io.IOException;
9   import java.io.InterruptedIOException;
10  import java.io.ObjectInputStream;
11  import java.net.DatagramPacket;
12  import java.net.DatagramSocket;
13  import java.net.InetAddress;
14  import java.util.ArrayList;
15  import java.util.Iterator;
16  import java.util.List;
17  
18  import javax.swing.SwingUtilities;
19  import org.apache.log4j.Logger;
20  import spellcast.net.NetConstants;
21  import spellcast.net.ServerStatus;
22  
23  class AvailableServerListUpdater implements Runnable {
24      /***
25       * The datagram packet size is 1024 bytes.  Any packet exceeding this length
26       * will be truncated.
27       */
28      private static final int PACKET_SIZE = 1024;
29  
30      /***
31       * The sleep for this many milliseconds before running the loop again.
32       * On each cycle we check for any new messages from servers and update the
33       * connection list.
34       */
35      private static final int SEND_REQUEST_SLEEP_TIMEOUT = 1 * 1000;
36  
37      /***
38       * Once the list has been updated this many times a new request is sent
39       * to query for new servers.  This gives us time to receive the responses
40       * from servers during each cycle.
41       */
42      private static final int SEND_REQUEST_AFTER_UPDATE_NUMBER = 5;
43  
44      private DatagramSocket socket;
45      private int numberOfUpdatesDone;
46      private Thread serverListThread;
47      private ServerListTableModel serverList;
48      private boolean isRunning;
49  
50      private static final Logger logger = Logger.getLogger("client.connect");
51      private static final Logger logger_net = Logger.getLogger("client.connect.net");
52  
53      AvailableServerListUpdater(ServerListTableModel serverList) {
54          this.serverList = serverList;
55          try {
56              InetAddress bindAddress = InetAddress.getLocalHost();
57              socket = new DatagramSocket(0, bindAddress);
58              socket.setSoTimeout(100);
59          }
60          catch (Exception ex) {
61              logger.error(
62                  "Could not create DatagramSocket for ServerStatusRequest/Responses",
63                  ex);
64          }
65      }
66  
67      public void run() {
68          while (isRunning) {
69              updateAvailableServers();
70              try {
71                  Thread.sleep(SEND_REQUEST_SLEEP_TIMEOUT);
72              }
73              catch (InterruptedException ex) {
74                  logger.error("Unexepectedly Interrupted.", ex);
75              }
76          }
77      }
78  
79      public void start() {
80          isRunning = true;
81          numberOfUpdatesDone = 0;
82          serverListThread = new Thread(this, "ServerListUpdater");
83          serverListThread.start();
84      }
85  
86      public void stop() {
87          isRunning = false;
88      }
89  
90      private void updateAvailableServers() {
91          if ((numberOfUpdatesDone % SEND_REQUEST_AFTER_UPDATE_NUMBER) == 0) {
92              numberOfUpdatesDone = 0;
93              sendServerStatusRequest();
94          }
95          numberOfUpdatesDone++;
96  
97          ServerStatus ss = null;
98          ArrayList tempList = new ArrayList(2);
99          while ((ss = receiveServerStatusResponse()) != null) {
100             logger_net.info(
101                 "\nReceived response\n"
102                     + "Address: "
103                     + ss.getServerIPAddress()
104                     + "\n"
105                     + "Game Name: "
106                     + ss.getGameName()
107                     + "\n");
108             tempList.add(ss);
109         }
110         if (tempList.size() != 0) {
111             SwingUtilities.invokeLater(new DoUpdateOnSwingThread(serverList, tempList));
112         }
113     }
114 
115     /***
116      * Sends a server list request on the broadcast address and SPELLCAST_NET_SERVERLIST_PORT.
117      * The packet sent contains 1 byte, as there is no information of value in the
118      * payload of the message.
119      */
120     private void sendServerStatusRequest() {
121         try {
122             InetAddress broadcast =
123                 InetAddress.getByName(NetConstants.SPELLCAST_NET_BROADCAST_INETADDRESS);
124             DatagramPacket send =
125                 new DatagramPacket(
126                     new byte[1],
127                     1,
128                     broadcast,
129                     NetConstants.SPELLCAST_NET_SERVERLIST_PORT);
130             socket.send(send);
131             logger_net.debug(
132                 "\nLocal Address = "
133                     + socket.getLocalAddress()
134                     + "\n"
135                     + "Local Port = "
136                     + socket.getLocalPort()
137                     + "\n");
138         }
139         catch (Exception ex) {
140             logger.error("Exception occurred.", ex);
141         }
142     }
143 
144     /***
145      * Receive any server status requests.  If there is a problem with 
146      * receiving the message then a null is returned and the error logged.
147      */
148     private ServerStatus receiveServerStatusResponse() {
149         ServerStatus result = null;
150         DatagramPacket response =
151             new DatagramPacket(new byte[PACKET_SIZE], PACKET_SIZE);
152         boolean messageReceived = false;
153 
154         try {
155             socket.receive(response);
156             logger_net.debug("Response Size = " + response.getLength());
157             messageReceived = true;
158         }
159         catch (InterruptedIOException ix) {
160             messageReceived = false;
161         }
162         catch (IOException ex) {
163             logger.error("Failed on socket.receive.", ex);
164         }
165 
166         if (messageReceived && response.getLength() != 0) {
167             try {
168                 ObjectInputStream ois =
169                     new ObjectInputStream(new ByteArrayInputStream(response.getData()));
170                 Object obj = ois.readObject();
171                 if (!(obj instanceof ServerStatus)) {
172                     logger.error("ServerStatus not received. Received " + obj.getClass().getName());
173                 }
174                 else {
175                     result = (ServerStatus) obj;
176                 }
177             }
178             catch (Exception ex) {
179                 logger.error("Failed to decode ServerStatus from message.", ex);
180             }
181         }
182         return result;
183     }
184 
185     /***
186      * This inner class is updates the server list on Swings Thread.
187      * This class is runnable so that <code>SwingUtilities.invokeLater</code>
188      * can run the necessary UI changes on Swing's thread.  
189      * This class requires the server list table model that is to be updated
190      * as well as a list of the servers to add to the model.  This class creates a copy
191      * of the list of servers.
192      */
193     class DoUpdateOnSwingThread implements Runnable {
194         private ServerListTableModel serverList;
195         private ArrayList newServers;
196 
197         DoUpdateOnSwingThread(ServerListTableModel serverList, List newServers) {
198             this.serverList = serverList;
199             this.newServers = new ArrayList(newServers);
200         }
201 
202         public void run() {
203             Iterator i = newServers.iterator();
204             while (i.hasNext()) {
205                 ServerStatus ss = (ServerStatus) i.next();
206                 serverList.add(ss);
207             }
208         }
209     }
210 }